import java.awt.*;
import java.awt.event.*;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

import javax.swing.*;

/**
 * Graphical user interface to a Tic Tac Toe application.
 * The GUI is incomplete because it has no associated player.
 * It also needs a routine for checking if one of the players
 * have got five marks in a row.
 */
public class TicTacToeGui extends JFrame implements Constants, ActionListener {
	/** Textfield showing what mark you use ('X' or 'O') */
	private JTextField id;
	/** TextArea giving feedback to the user */
	private TextArea display;
	/** The panel containing the board */
	private JPanel boardPanel;
	/** The squares of the board */
	private Square board[][];
	/** The menu bar */
	private JMenuBar menuBar;
	/** The game submenu */
	private JMenu gameMenu;
	/** Game submenu choices */
	private JMenuItem newGameItem, quitItem;
	
	/** The name of the player using this GUI */
	private String myName;
	/** The mark used by this player ('X' or 'O') */
	private char myMark;
	/** Boolean for my turn or opponents turn */
	private boolean myTurn;
	/** Opponent and me */
	private TTTRmi opponent;
	private TTTRmiImpl me;
	
	public final static int PORT = 9999;

	/**
	 * Creates a new GUI.
	 * @param name	The name of that player.
	 * @param mark	The mark used by that player.
	 */
	public TicTacToeGui(String name, char mark, String address) {
		myName = name;
		myMark = mark;

		// Create GUI components:
		// The display at the bottom:
		display = new TextArea("", 4, 30, TextArea.SCROLLBARS_VERTICAL_ONLY);
		display.setEditable(false);
		// The name field at the top:
		id = new JTextField();
		id.setEditable(false);
		id.setText(myName + " er: " + myMark);
		// The board:
		JPanel gridPanel = new JPanel();
		gridPanel.setLayout(new GridLayout(BOARD_SIZE, BOARD_SIZE, 0, 0));
		board = new Square[BOARD_SIZE][BOARD_SIZE];
		for(int row = 0; row < board.length; row++) 
			for(int col = 0; col < board[row].length; col++) {
				board[row][col] = new Square(this, row, col);
				gridPanel.add(board[row][col]);
			}
		boardPanel = new JPanel();
		boardPanel.add(gridPanel);

		// Place the components:
		Container cp = getContentPane();
		cp.setLayout(new BorderLayout());
		cp.add("South", display);
		cp.add("North", id);
		cp.add("Center", boardPanel);

		// Create the menu.
		menuBar = new JMenuBar();
		setJMenuBar(menuBar);
		gameMenu = new JMenu("Game");
		gameMenu.setMnemonic(KeyEvent.VK_G);
		menuBar.add(gameMenu);
		newGameItem = new JMenuItem("New game", KeyEvent.VK_N);
		newGameItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
		gameMenu.add(newGameItem);
		quitItem = new JMenuItem("Quit", KeyEvent.VK_Q);
		quitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, ActionEvent.ALT_MASK));
		gameMenu.add(quitItem);

		// Add listeners
		newGameItem.addActionListener(this);
		quitItem.addActionListener(this);
		// Add an anonymous WindowListener which calls quit() when the window is closing
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				quit();
			}
		});

		// Place and show the window:
		setTitle("Tic Tac Toe: " + name);
		setSize(WIN_WIDTH, WIN_HEIGHT);
		setLocation(200, 200);
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		setVisible(true);
		
		// Init the game
		init(address);
	}

	/**
	 * Called by the Square class when an empty square is clicked.
	 * @param row		The row of the square that was clicked.
	 * @param column	The column of the square that was clicked.
	 */
	public void squareClicked(int row, int column) {
		if (opponent == null || !myTurn) return;
		setMark(row, column, myMark);
		try {
			opponent.mark(row, column);
			myTurn = false;
			if (isVictory(myMark, row, column)) {
				opponent.notifyVictory();
				println("Fem på rad! Du har vunnet!");
			}
		} catch (RemoteException rex) {
			println("Det oppstod en feil: " + rex.getMessage());
		}
	}

	/**
	 * Marks the specified square of the board with the specified mark.
	 * @param row		The row of the square to mark.
	 * @param column	The column of the square to mark.
	 * @param mark		The mark to use.
	 */
	public void setMark(int row, int column, char mark) {
		board[row][column].setMark(mark);
		repaint();
	}

	/**
	 * Called when a menu item has been selected.
	 * @param e	The ActionEvent that occured.
	 */
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == newGameItem)
			newGame();
		else if(e.getSource() == quitItem)
			quit();
	}

	/**
	 * Starts a new game, if the user confirms it in a dialog box.
	 */
	public void newGame() {
		// This method must be modified!
		if (opponent == null) return;
		if(JOptionPane.showConfirmDialog(this, "Are you sure you want to start a new game?", "Start over?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
			clearBoard();
			try {
				opponent.reset();
				println("Starter nytt spill...");
			} catch (RemoteException rex) {
				println("En feil oppstod: " + rex.getMessage());
			}
			setMyTurn(myMark == 'X' ? true : false);
		}
	}

	/**
	 * Removes all marks from the board.
	 */
	public void clearBoard() {
		for(int row = 0; row < board.length; row++)
			for(int col = 0; col < board[row].length; col++)
				board[row][col].setMark(' ');
		repaint();
	}

	/**
	 * Exits the game, if the user confirms it in a dialog box.
	 * Should notify the opponent that we left the game.
	 */
	public void quit() {
		// This method should be modified! 
		if(JOptionPane.showConfirmDialog(this, "Are you sure you want to quit?", "Really quit?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
			if (opponent != null) {
				try {
					opponent.disconnect();
				} catch (RemoteException rex) {
					println("En feil oppstod: " + rex.getMessage());
				}
			} else if (me.isServer()) {
				try {
					// Unbind RMI objektet og avslutte server
					Naming.unbind(me.getURL());	
				} catch (Exception e) {
					System.err.println("En feil oppstod: " + e.getMessage());
				}
			}
			System.exit(0);
		}
	}

	/**
	 * Outputs a message to the user.
	 * @param s	The string to display. Adds a newline to the end of the string.
	 */
	public void println(String s) {
		display.append(s + "\n");
	}

	/**
	 * Outputs a message to the user.
	 * @param s	The string to display.
	 */
	public void print(String s) {
		display.append(s);
	}
	
	/**
	 * Sets who's turn
	 * @param myTurn
	 */
	public void setMyTurn(boolean myTurn) {
		this.myTurn = myTurn;
	}
	
	/**
	 * Gets who's turn
	 * @return
	 */
	public boolean isMyTurn() {
		return myTurn;
	}
	
	/**
	 * Gets the RMI object that you're battling
	 * @return
	 */
	public TTTRmi getOpponent() {
		return opponent;
	}
	
	/**
	 * Sets the RMI object that you wish to battle
	 * @param opponent
	 */
	public void setOpponent(TTTRmi opponent) {
		this.opponent = opponent;
	}
	
	public String getPlayerName() {
		return myName;
	}
	
	/**
	 * Either connect to the server at address og create a server and listen for connections
	 * @param address
	 */
	public void init(String address) {
		String rmiUrl = "rmi://" + address + "/TTTRmi";
		
		try {
			me = new TTTRmiImpl(this);
		} catch (Exception e) {
			println("En feil oppstod: " + e.getMessage());
			return;
		}
		
		// Looking for a server first
		try {
			// Check for RMI resource at rmiURL
			setOpponent((TTTRmi)Naming.lookup(rmiUrl));
		} catch (NotBoundException nex) {
			println("Fant ingen server... Oppretter");
		} catch (Exception ex) {
			println("En feil oppstod: " + ex.getMessage());
		}
		
		// Start or connect to a server
		// Start server
		if (getOpponent() == null) {
			// Create a server by binding a RMI resource to rmiURL
			me.bind(rmiUrl);
			setMyTurn(true);
		}
		else {
			// Connecting
			setName("Lars");
			myMark = 'O';
			setMyTurn(false);
			id.setText(getName() + " er: " + myMark);
			try {
				// Connect to the server
				String opn = getOpponent().connect(getName(), myMark, me);
				println("Koblet til " + opn + "...");
			} catch (RemoteException re) {
				println("Klarte ikke koble til: " + re.getMessage());
			}
		}
	}
	
	private boolean isVictory(char mark, int row, int col) {
		int rowCount = 0;		// Antall merker vertikalt
		int colCount = 0;		// Antall merker horisontalt
		int lrDiag = 0;			// Antall merker diagonalt venstre->høyre
		int rlDiag = 0;			// Antall merker diagonalt høyre->venstre
		
		// Sjekker hele brettet (ikke optimalt)
		for (int i = 0; i < board.length; i++) {
			for (int j = 0; j < board.length; j++) {
				// Teller vertikalt
				if (board[i][j].getMark() == mark) rowCount++;
				else if (rowCount != 0) rowCount = 0;
				if (rowCount == 5) return true;
				
				// Teller horisontalt
				if (board[j][i].getMark() == mark) colCount++;
				else if (colCount != 0) colCount = 0;
				if (colCount == 5) return true;
			}
		}
		
		// Sjekker diagonalt venstre->høyre
		int x = col;
		int y = row;
		while (x >= 0 && x < board.length && y >= 0 && y < board.length) {
			if (board[y][x].getMark() == mark) lrDiag++;
			else if (lrDiag != 0) lrDiag = 0;
			if (lrDiag == 5) return true;
			x++;
			y++;
		}
		
		// Sjekker diagonalt høyre->venstre
		while (x >= 0 && x < board.length && y >= 0 && y < board.length) {
			if (board[y][x].getMark() == mark) rlDiag++;
			else if (rlDiag != 0) rlDiag = 0;
			if (rlDiag == 5) return true;
			x--;
			y++;
		}
		return false;
	}

	/**
	 * Starts up a GUI without an associated player, in order
	 * to check the appearance of the GUI.
	 */
	public static void main(String args[]) {
		String address = "";
		if (args.length > 0) {
			address = args[0] + ":" + PORT;
		}
		System.setSecurityManager(new LiberalSecurityManager());
		TicTacToeGui hisGui = new TicTacToeGui("Ottar", 'X', address);
	}
}
